package com.esdk.cipher;

import java.io.ByteArrayOutputStream;
import java.net.URLEncoder;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.Cipher;

import com.esdk.esdk;

/**
 * <p>
 * RSA公钥/私钥/签名工具包
 * </p>
 * <p>
 * 罗纳德·李维斯特（Ron [R]ivest）、阿迪·萨莫尔（Adi [S]hamir）和伦纳德·阿德曼（Leonard [A]dleman）
 * </p>
 * <p>
 * 字符串格式的密钥在未在特殊说明情况下都为BASE64编码格式<br/>
 * 由于非对称加密速度极其缓慢，一般文件不使用它来加密而是使用对称加密，<br/>
 * 非对称加密算法可以用来对对称加密的密钥加密，这样保证密钥的安全也就保证了数据的安全
 * </p>
 * 
 * @author IceWee
 * @date 2012-4-26
 * @version 1.0
 */
public class RSAUtils{

	/**
	 * 加密算法RSA
	 */
	public static final String KEY_ALGORITHM="RSA";

	/**
	 * 签名算法
	 */
	public static final String SIGNATURE_ALGORITHM="MD5withRSA";

	/**
	 * 获取公钥的key
	 */
	private static final String PUBLIC_KEY="RSAPublicKey";

	/**
	 * 获取私钥的key
	 */
	private static final String PRIVATE_KEY="RSAPrivateKey";

	/**
	 * RSA最大加密明文大小
	 */
	private static final int MAX_ENCRYPT_BLOCK=117;

	/**
	 * RSA最大解密密文大小
	 */
	private static final int MAX_DECRYPT_BLOCK=128;

	/**
	 * <p>
	 * 生成密钥对(公钥和私钥)
	 * </p>
	 * 
	 * @return
	 * @throws Exception
	 */
	public static Map<String,Object> genKeyPair() throws Exception{
		KeyPairGenerator keyPairGen=KeyPairGenerator.getInstance(KEY_ALGORITHM);
		keyPairGen.initialize(1024);
		KeyPair keyPair=keyPairGen.generateKeyPair();
		RSAPublicKey publicKey=(RSAPublicKey)keyPair.getPublic();
		RSAPrivateKey privateKey=(RSAPrivateKey)keyPair.getPrivate();
		Map<String,Object> keyMap=new HashMap<String,Object>(2);
		keyMap.put(PUBLIC_KEY,publicKey);
		keyMap.put(PRIVATE_KEY,privateKey);
		return keyMap;
	}

	/**
	 * <p>
	 * 用私钥对信息生成数字签名
	 * </p>
	 * 
	 * @param data
	 *          已加密数据
	 * @param privateKey
	 *          私钥(BASE64编码)
	 * 
	 * @return
	 * @throws Exception
	 */
	public static String sign(byte[] data,String privateKey) throws Exception{
		byte[] keyBytes= Base64.getDecoder().decode(privateKey);
		PKCS8EncodedKeySpec pkcs8KeySpec=new PKCS8EncodedKeySpec(keyBytes);
		KeyFactory keyFactory=KeyFactory.getInstance(KEY_ALGORITHM);
		PrivateKey privateK=keyFactory.generatePrivate(pkcs8KeySpec);
		Signature signature=Signature.getInstance(SIGNATURE_ALGORITHM);
		signature.initSign(privateK);
		signature.update(data);
		return Base64.getEncoder().encodeToString(signature.sign());
	}

	/**
	 * <p>
	 * 校验数字签名
	 * </p>
	 * 
	 * @param data
	 *          已加密数据
	 * @param publicKey
	 *          公钥(BASE64编码)
	 * @param sign
	 *          数字签名
	 * 
	 * @return
	 * @throws Exception
	 * 
	 */
	public static boolean verify(byte[] data,String publicKey,String sign) throws Exception{
		byte[] keyBytes=Base64.getDecoder().decode(publicKey);
		X509EncodedKeySpec keySpec=new X509EncodedKeySpec(keyBytes);
		KeyFactory keyFactory=KeyFactory.getInstance(KEY_ALGORITHM);
		PublicKey publicK=keyFactory.generatePublic(keySpec);
		Signature signature=Signature.getInstance(SIGNATURE_ALGORITHM);
		signature.initVerify(publicK);
		signature.update(data);
		return signature.verify(Base64.getDecoder().decode(sign));
	}

	/**
	 * <P>
	 * 私钥解密
	 * </p>
	 * 
	 * @param encryptedData
	 *          已加密数据
	 * @param privateKey
	 *          私钥(BASE64编码)
	 * @return
	 * @throws Exception
	 */
	public static byte[] decryptByPrivateKey(byte[] encryptedData,String privateKey) throws Exception{
		byte[] keyBytes=Base64.getDecoder().decode(privateKey);
		PKCS8EncodedKeySpec pkcs8KeySpec=new PKCS8EncodedKeySpec(keyBytes);
		KeyFactory keyFactory=KeyFactory.getInstance(KEY_ALGORITHM);
		Key privateK=keyFactory.generatePrivate(pkcs8KeySpec);
		Cipher cipher=Cipher.getInstance(keyFactory.getAlgorithm());
		cipher.init(Cipher.DECRYPT_MODE,privateK);
		int inputLen=encryptedData.length;
		ByteArrayOutputStream out=new ByteArrayOutputStream();
		int offSet=0;
		byte[] cache;
		int i=0;
		// 对数据分段解密
		while(inputLen-offSet>0){
			if(inputLen-offSet>MAX_DECRYPT_BLOCK){
				cache=cipher.doFinal(encryptedData,offSet,MAX_DECRYPT_BLOCK);
			}else{
				cache=cipher.doFinal(encryptedData,offSet,inputLen-offSet);
			}
			out.write(cache,0,cache.length);
			i++;
			offSet=i*MAX_DECRYPT_BLOCK;
		}
		byte[] decryptedData=out.toByteArray();
		out.close();
		return decryptedData;
	}

	/**
	 * <p>
	 * 公钥解密
	 * </p>
	 * 
	 * @param encryptedData
	 *          已加密数据
	 * @param publicKey
	 *          公钥(BASE64编码)
	 * @return
	 * @throws Exception
	 */
	public static byte[] decryptByPublicKey(byte[] encryptedData,String publicKey) throws Exception{
		byte[] keyBytes=Base64.getDecoder().decode(publicKey);
		X509EncodedKeySpec x509KeySpec=new X509EncodedKeySpec(keyBytes);
		KeyFactory keyFactory=KeyFactory.getInstance(KEY_ALGORITHM);
		Key publicK=keyFactory.generatePublic(x509KeySpec);
		Cipher cipher=Cipher.getInstance(keyFactory.getAlgorithm());
		cipher.init(Cipher.DECRYPT_MODE,publicK);
		int inputLen=encryptedData.length;
		ByteArrayOutputStream out=new ByteArrayOutputStream();
		int offSet=0;
		byte[] cache;
		int i=0;
		// 对数据分段解密
		while(inputLen-offSet>0){
			if(inputLen-offSet>MAX_DECRYPT_BLOCK){
				cache=cipher.doFinal(encryptedData,offSet,MAX_DECRYPT_BLOCK);
			}else{
				cache=cipher.doFinal(encryptedData,offSet,inputLen-offSet);
			}
			out.write(cache,0,cache.length);
			i++;
			offSet=i*MAX_DECRYPT_BLOCK;
		}
		byte[] decryptedData=out.toByteArray();
		out.close();
		return decryptedData;
	}

	/**
	 * <p>
	 * 公钥加密
	 * </p>
	 * 
	 * @param data
	 *          源数据
	 * @param publicKey
	 *          公钥(BASE64编码)
	 * @return
	 * @throws Exception
	 */
	public static byte[] encryptByPublicKey(byte[] data,String publicKey) throws Exception{
		byte[] keyBytes=Base64.getDecoder().decode(publicKey);
		X509EncodedKeySpec x509KeySpec=new X509EncodedKeySpec(keyBytes);
		KeyFactory keyFactory=KeyFactory.getInstance(KEY_ALGORITHM);
		Key publicK=keyFactory.generatePublic(x509KeySpec);
		// 对数据加密
		Cipher cipher=Cipher.getInstance(keyFactory.getAlgorithm());
		cipher.init(Cipher.ENCRYPT_MODE,publicK);
		int inputLen=data.length;
		ByteArrayOutputStream out=new ByteArrayOutputStream();
		int offSet=0;
		byte[] cache;
		int i=0;
		// 对数据分段加密
		while(inputLen-offSet>0){
			if(inputLen-offSet>MAX_ENCRYPT_BLOCK){
				cache=cipher.doFinal(data,offSet,MAX_ENCRYPT_BLOCK);
			}else{
				cache=cipher.doFinal(data,offSet,inputLen-offSet);
			}
			out.write(cache,0,cache.length);
			i++;
			offSet=i*MAX_ENCRYPT_BLOCK;
		}
		byte[] encryptedData=out.toByteArray();
		out.close();
		return encryptedData;
	}

	/**
	 * <p>
	 * 私钥加密
	 * </p>
	 * 
	 * @param data
	 *          源数据
	 * @param privateKey
	 *          私钥(BASE64编码)
	 * @return
	 * @throws Exception
	 */
	public static byte[] encryptByPrivateKey(byte[] data,String privateKey) throws Exception{
		byte[] keyBytes=Base64.getDecoder().decode(privateKey);
		PKCS8EncodedKeySpec pkcs8KeySpec=new PKCS8EncodedKeySpec(keyBytes);
		KeyFactory keyFactory=KeyFactory.getInstance(KEY_ALGORITHM);
		Key privateK=keyFactory.generatePrivate(pkcs8KeySpec);
		Cipher cipher=Cipher.getInstance(keyFactory.getAlgorithm());
		cipher.init(Cipher.ENCRYPT_MODE,privateK);
		int inputLen=data.length;
		ByteArrayOutputStream out=new ByteArrayOutputStream();
		int offSet=0;
		byte[] cache;
		int i=0;
		// 对数据分段加密
		while(inputLen-offSet>0){
			if(inputLen-offSet>MAX_ENCRYPT_BLOCK){
				cache=cipher.doFinal(data,offSet,MAX_ENCRYPT_BLOCK);
			}else{
				cache=cipher.doFinal(data,offSet,inputLen-offSet);
			}
			out.write(cache,0,cache.length);
			i++;
			offSet=i*MAX_ENCRYPT_BLOCK;
		}
		byte[] encryptedData=out.toByteArray();
		out.close();
		return encryptedData;
	}

	/**
	 * <p>
	 * 获取私钥
	 * </p>
	 * 
	 * @param keyMap
	 *          密钥对
	 * @return
	 * @throws Exception
	 */
	public static String getPrivateKey(Map<String,Object> keyMap) throws Exception{
		Key key=(Key)keyMap.get(PRIVATE_KEY);
		return Base64.getEncoder().encodeToString(key.getEncoded());
	}

	/**
	 * <p>
	 * 获取公钥
	 * </p>
	 * 
	 * @param keyMap
	 *          密钥对
	 * @return
	 * @throws Exception
	 */
	public static String getPublicKey(Map<String,Object> keyMap) throws Exception{
		Key key=(Key)keyMap.get(PUBLIC_KEY);
		return Base64.getEncoder().encodeToString(key.getEncoded());
	}

	public static void main(String[] args) throws  Exception{
		Map map=genKeyPair();
		System.out.println(" PUBLIC KEY: "+getPublicKey(map));
		System.out.println("PRIVATE KEY: "+getPrivateKey(map));
		String privateKey="MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALECCmfaSubzjrStyo6XTGtwJJLZB1w2KG8GIt4a9VrIWHsF6tKskx7T/bUevQqhSMkXPsaRMxQk6KLKhxdmGdLNXFjZuWHXTwPv6NO7ni2g+0NQSvw9r1UqLaHQBd+LLTH0xRNrgcKVH82MhqkhBZv/zmeXLU9T2QsHDv3rm1aLAgMBAAECgYAj8jMkxfriDeIUJRr2fBlD1EFJJRPOkR0C9u8LxdO/vOHjEd+PKwaxgwGJz5U6XGiIldTkxEXoOFqZ/KEUimKBygEq5cTQG+wAjWIZjKXCdQR5rojLuRrDNmwFZyYcIIlKuI4SgU3xrTov1Ni5PrWpxjvHcwZ4k9vBfZfiPlv5IQJBAPypv9/YM5jV63XcDYT2agunsri31tYpkypBi3WFJqfIkAgCPAu3Phn8ynrVFwmbFYNVxCV+p6KsLFk5k2eP0RECQQCzWICRZmyvsE97U3va/n45zp4YTmArZ0iGxmi79Q0CmoY6r71cb39SnGw++0wMUbjTk43xZ6WM9+issQuum63bAkEApKO0XReT36adrQo8YQT06y1Wn1lkC9/BfsqBJo4iNzjQ6fcSy4uXUvXPtyS9w0ukRBWSH+CdObo5l9aVv+kOEQJABzBd/vYPFz/G/9eJ2G5pGuIYXjsOCc9bDeP4IMii297b0JBo08K4ZNRWVnP2SWwhL4Hzp6CS90Kctgdmw3oEDwJBAPYmtQgLb8x/OpihQN77JEe83dXPrTLN83GNUotjdyywGGUu9yULSY13BIWJ+D4Jr/Vmw4EM6a17UjY8oX0Naqo=";
		String publicKey="MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCxAgpn2krm8460rcqOl0xrcCSS2QdcNihvBiLeGvVayFh7BerSrJMe0/21Hr0KoUjJFz7GkTMUJOiiyocXZhnSzVxY2blh108D7+jTu54toPtDUEr8Pa9VKi2h0AXfiy0x9MUTa4HClR/NjIapIQWb/85nly1PU9kLBw7965tWiwIDAQAB";
		String decodeText="ffybaby123+"+esdk.time.format(new Date());
		System.out.println("decodeText:"+decodeText);
		byte[] v=RSAUtils.encryptByPublicKey(decodeText.getBytes(),publicKey); //" eg.: ffybaby+2016-12-31 23:59:59"
		String encodeText=Base64.getEncoder().encodeToString(v);
		System.out.println("encodeText:"+encodeText);
		System.out.println("urlencode:"+URLEncoder.encode(Base64.getEncoder().encodeToString(v),"utf8")); //如果要做为url参数，需要先URLEnocde处理，注意在解密时，就不能用URLDecode,否则就是二次URLDeocde了。
		String decodedText=new String(RSAUtils.decryptByPrivateKey(Base64.getDecoder().decode(encodeText.replaceAll(" ","+")),privateKey));
		System.out.println(decodedText.equals(decodeText));
	}
}